Completed
Pull Request — master (#179)
by Olivier
09:46
created

SlideShow._calculateBackgroundColour   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 63
rs 8.6498

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
/* global DOMPurify */
2
(function ($, OC, OCA, t) {
3
	"use strict";
4
	/**
5
	 * Slideshow featuring zooming
6
	 *
7
	 * @constructor
8
	 */
9
	var SlideShow = function () {
10
	};
11
12
	SlideShow.prototype = {
13
		slideshowTemplate: null,
14
		container: null,
15
		zoomablePreviewContainer: null,
16
		controls: null,
17
		imageCache: {},
18
		/** {Image} */
19
		currentImage: null,
20
		errorLoadingImage: false,
21
		onStop: null,
22
		zoomablePreview: null,
23
		active: false,
24
		backgroundToggle: false,
25
		// We need 6 hexas for comparison reasons
26
		darkBackgroundColour: '#000000',
27
		lightBackgroundColour: '#ffffff',
28
29
		/**
30
		 * Initialises the slideshow
31
		 *
32
		 * @param {boolean} autoPlay
33
		 * @param {number} interval
34
		 * @param {Array} features
35
		 */
36
		init: function (autoPlay, interval, features) {
37
			if (features.indexOf('background_colour_toggle') > -1) {
38
				this.backgroundToggle = true;
39
			}
40
41
			return $.when(this._getSlideshowTemplate()).then(function ($tmpl) {
42
				// Move the slideshow outside the content so we can hide the content
43
				$('body').append($tmpl);
44
				this.container = $('#slideshow');
45
				this.zoomablePreviewContainer = this.container.find('.bigshotContainer');
46
				this.zoomablePreview = new SlideShow.ZoomablePreview(this.container);
47
				this.controls =
48
					new SlideShow.Controls(
49
						this,
50
						this.container,
51
						this.zoomablePreview,
52
						interval,
53
						features);
54
				this.controls.init();
55
56
				this._initControlsAutoFader();
57
58
				// Replace all Owncloud svg images with png images for ancient browsers
59
				if (!OC.Util.hasSVGSupport()) {
60
					OC.Util.replaceSVG(this.$el);
61
				}
62
63
				// Only modern browsers can manipulate history
64
				if (history && history.pushState) {
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable history is declared in the current environment, consider using typeof history === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
65
					// Stop the slideshow when backing out.
66
					$(window).bind('popstate.slideshow', function () {
67
						if (this.active === true) {
68
							this.active = false;
69
							this.controls.stop();
70
						}
71
					}.bind(this));
72
				}
73
			}.bind(this)).fail(function () {
74
				OC.Notification.show(t('core', 'Error loading slideshow template'));
75
			});
76
		},
77
78
		/**
79
		 * Refreshes the slideshow's data
80
		 *
81
		 * @param {{name:string, url: string, path: string, fallBack: string}[]} images
82
		 * @param {boolean} autoPlay
83
		 */
84
		setImages: function (images, autoPlay) {
85
			this._hideImage();
86
			this.images = images;
87
			this.controls.update(images, autoPlay);
88
		},
89
90
		/**
91
		 * Launches the slideshow
92
		 *
93
		 * @param {number} index
94
		 *
95
		 * @returns {*}
96
		 */
97
		show: function (index) {
98
			this.hideErrorNotification();
99
			this.active = true;
100
			this.container.show();
101
			this.container.css('background-position', 'center');
102
			this._hideImage();
103
			var currentImageId = index;
104
			return this.loadImage(this.images[index]).then(function (img) {
105
				this.container.css('background-position', '-10000px 0');
106
107
				// check if we moved along while we were loading
108
				if (currentImageId === index) {
109
					var image = this.images[index];
110
					var transparent = this._isTransparent(image.mimeType);
111
					this.controls.showActionButtons(transparent, Gallery.token, image.permissions);
0 ignored issues
show
Bug introduced by
The variable Gallery seems to be never declared. If this is a global, consider adding a /** global: Gallery */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Bug introduced by
Gallery does not seem to be defined.
Loading history...
112
					this.errorLoadingImage = false;
113
					this.currentImage = img;
114
115
					var backgroundColour = this.darkBackgroundColour;
116
					if (transparent) {
117
						backgroundColour = this.lightBackgroundColour;
118
					}
119
					if (image.backGroundColour !== null) {
120
						backgroundColour = image.backGroundColour;
121
					}
122
					img.setAttribute('alt', image.name);
123
					$(img).css('position', 'absolute');
124
					$(img).css('background-color', backgroundColour);
125
					if (transparent && this.backgroundToggle === true) {
126
						var $border = 30 / window.devicePixelRatio;
127
						$(img).css('outline', $border + 'px solid ' + backgroundColour);
128
					}
129
130
					this.zoomablePreview.startBigshot(img, this.currentImage, image.mimeType);
131
132
					this._setUrl(image.path);
133
					this.controls.show(currentImageId);
134
				}
135
			}.bind(this), function () {
136
				// Don't do anything if the user has moved along while we were loading as it would
137
				// mess up the index
138
				if (currentImageId === index) {
139
					this.errorLoadingImage = true;
140
					this.showErrorNotification(null);
141
					this._setUrl(this.images[index].path);
142
					this.images.splice(index, 1);
143
					this.controls.updateControls(this.images, this.errorLoadingImage);
144
				}
145
			}.bind(this));
146
		},
147
148
		/**
149
		 * Loads the image to show in the slideshow and preloads the next one
150
		 *
151
		 * @param {Object} preview
152
		 *
153
		 * @returns {*}
154
		 */
155
		loadImage: function (preview) {
156
			var url = preview.url;
157
			var mimeType = preview.mimeType;
158
159
			if (!this.imageCache[url]) {
160
				this.imageCache[url] = new $.Deferred();
161
				var image = new Image();
0 ignored issues
show
Bug introduced by
The variable Image seems to be never declared. If this is a global, consider adding a /** global: Image */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
162
163
				image.onload = function () {
164
					preview.backGroundColour = this._calculateBackgroundColour(image, mimeType);
165
					if (this.imageCache[url]) {
166
						this.imageCache[url].resolve(image);
167
					}
168
				}.bind(this);
169
				image.onerror = function () {
170
					if (this.imageCache[url]) {
171
						this.imageCache[url].reject(url);
172
					}
173
				}.bind(this);
174
				if (mimeType === 'image/svg+xml') {
175
					image.src = this._getSVG(url);
176
				} else {
177
					image.src = url;
178
				}
179
			}
180
			return this.imageCache[url];
181
		},
182
183
		/**
184
		 * Shows a new image in the slideshow and preloads the next in the list
185
		 *
186
		 * @param {number} current
187
		 * @param {Object} next
188
		 */
189
		next: function (current, next) {
190
			this.show(current).then(function () {
191
				// Preloads the next image in the list
192
				this.loadImage(next);
193
			}.bind(this));
194
		},
195
196
		/**
197
		 * Calculates the luminance of an image
198
		 *
199
		 * @param {*} image
200
		 * @param {string} mimeType
201
		 *
202
		 * @returns {string}
203
		 * @private
204
		 */
205
		_calculateBackgroundColour: function (image, mimeType) {
0 ignored issues
show
Unused Code introduced by
mimeType does not seem to be used.
Loading history...
206
			var backgroundColour = this.darkBackgroundColour;
207
			if (!this._isTransparent(image.mimeType)) {
208
				return backgroundColour;
209
			}
210
211
			// The name has to be 'canvas'
212
			var lumiCanvas = document.createElement('canvas');
213
			lumiCanvas.width = 200;
214
			lumiCanvas.height = 200;
215
			var lumiCtx = lumiCanvas.getContext('2d');
216
217
			lumiCanvas.height = lumiCanvas.width * (image.height / image.width);
218
			lumiCtx.drawImage(image, 0, 0, lumiCanvas.width, lumiCanvas.height);
219
			var imgData = lumiCtx.getImageData(0, 0, lumiCanvas.width, lumiCanvas.height);
220
			var pix = imgData.data;
221
			var pixelArraySize = pix.length;
222
			var numberOfSamples = 4000; // Seems to be the sweet spot
223
			var totalLuminance = 0;
224
			var sampleNumber = 1;
225
			var averageLuminance;
226
			var totalAlpha = 0;
227
			var alphaLevel;
228
			var red = 0;
229
			var green = 0;
230
			var blue = 0;
231
			var alpha = 0;
232
			var lum = 0;
233
234
			var sampleCounter = 0;
235
236
			var sampleSize = Math.floor(pixelArraySize / numberOfSamples);
237
238
			// i += 4 because 4 colours for every pixel
239
			for (var i = 0, n = pixelArraySize; i < n; i += 4 * sampleSize) {
240
				sampleCounter++;
241
				alpha = pix[i + 3] / 255;
242
				totalAlpha += alpha;
243
				if (Math.ceil(alpha * 100) / 100 > 0.1) {
244
					red = pix[i];
245
					green = pix[i + 1];
246
					blue = pix[i + 2];
247
					// Luminance formula from
248
					// http://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
249
					lum = (red + red + green + green + green + blue) / 6;
250
					//lum = (red * 0.299 + green * 0.587 + blue * 0.114 );
251
					totalLuminance += lum * alpha;
252
					sampleNumber++;
253
				}
254
			}
255
256
			// Deletes the canvas
257
			lumiCanvas = null;
0 ignored issues
show
Unused Code introduced by
The assignment to lumiCanvas seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
258
259
			// Calculate the optimum background colour for this image
260
			averageLuminance = Math.ceil((totalLuminance / sampleNumber) * 100) / 100;
261
			alphaLevel = Math.ceil((totalAlpha / (numberOfSamples / 4)) * 100);
262
			if (averageLuminance < 50 && alphaLevel < 90) {
263
				backgroundColour = this.lightBackgroundColour;
264
			}
265
266
			return backgroundColour;
267
		},
268
269
		/**
270
		 * Stops the slideshow
271
		 */
272
		stop: function () {
273
			this.active = false;
274
			this.images = null;
275
			this._hideImage();
276
			if (this.onStop) {
277
				this.onStop();
278
			}
279
		},
280
281
		/**
282
		 * Sends the current image as a download
283
		 *
284
		 * @param {string} downloadUrl
285
		 *
286
		 * @returns {boolean}
287
		 */
288
		getImageDownload: function (downloadUrl) {
289
			OC.redirect(downloadUrl);
290
			return false;
291
		},
292
293
		/**
294
		 * Changes the colour of the background of the image
295
		 */
296
		toggleBackground: function () {
297
			var toHex = function (x) {
298
				return ("0" + parseInt(x).toString(16)).slice(-2);
299
			};
300
			var container = this.zoomablePreviewContainer.children('img');
301
			var rgb = container.css('background-color').match(/\d+/g);
302
			var hex = "#" + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
303
			var $border = 30 / window.devicePixelRatio;
304
305
			// Grey #363636
306
			if (hex === this.darkBackgroundColour) {
307
				container.css('background-color', this.lightBackgroundColour);
308
				if (this.backgroundToggle === true) {
309
					container.css('outline', $border + 'px solid ' + this.lightBackgroundColour);
310
				}
311
			} else {
312
				container.css('background-color', this.darkBackgroundColour);
313
				if (this.backgroundToggle === true) {
314
					container.css('outline', $border + 'px solid ' + this.darkBackgroundColour);
315
				}
316
			}
317
		},
318
319
		/**
320
		 * Shows an error notification
321
		 *
322
		 * @param {string} message
323
		 */
324
		showErrorNotification: function (message) {
325
			if ($.isEmptyObject(message)) {
326
				message = t('gallery',
327
					'<strong>Error!</strong> Could not generate a preview of this file.<br>' +
328
					'Please go to the next slide while we remove this image from the slideshow');
329
			}
330
			this.container.find('.notification').html(message);
331
			this.container.find('.notification').show();
332
			this.controls.hideButton('.changeBackground');
333
		},
334
335
		/**
336
		 * Hides the error notification
337
		 */
338
		hideErrorNotification: function () {
339
			this.container.find('.notification').hide();
340
			this.container.find('.notification').html('');
341
		},
342
343
		/**
344
		 * Removes a specific button from the interface
345
		 *
346
		 * @param button
347
		 */
348
		removeButton: function (button) {
349
			this.controls.removeButton(button);
350
		},
351
352
		/**
353
		 * Deletes an image from the slideshow
354
		 *
355
		 * @param {object} image
356
		 * @param {number} currentIndex
357
		 */
358
		deleteImage: function (image, currentIndex) {
359
			// These are Gallery specific commands to be replaced
360
			// which should sit somewhere else
361
			if (!window.galleryFileAction) {
362
				delete Gallery.imageMap[image.path];
0 ignored issues
show
Bug introduced by
The variable Gallery seems to be never declared. If this is a global, consider adding a /** global: Gallery */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Bug introduced by
Gallery does not seem to be defined.
Loading history...
363
				delete Thumbnails.map[image.file];
0 ignored issues
show
Bug introduced by
The variable Thumbnails seems to be never declared. If this is a global, consider adding a /** global: Thumbnails */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Bug introduced by
Thumbnails does not seem to be defined.
Loading history...
364
				Gallery.albumMap[Gallery.currentAlbum].images.splice(currentIndex, 1);
0 ignored issues
show
Bug introduced by
Gallery does not seem to be defined.
Loading history...
365
				Gallery.view.init(Gallery.currentAlbum);
0 ignored issues
show
Bug introduced by
Gallery does not seem to be defined.
Loading history...
366
			}
367
		},
368
369
		/**
370
		 * Automatically fades the controls after 3 seconds
371
		 *
372
		 * @private
373
		 */
374
		_initControlsAutoFader: function () {
375
			var inactiveCallback = function () {
376
				this.container.addClass('inactive');
377
			}.bind(this);
378
			var inactiveTimeout = setTimeout(inactiveCallback, 3000);
379
380
			this.container.on('mousemove touchstart', function () {
381
				this.container.removeClass('inactive');
382
				clearTimeout(inactiveTimeout);
383
				inactiveTimeout = setTimeout(inactiveCallback, 3000);
384
			}.bind(this));
385
		},
386
387
		/**
388
		 * Simplest way to detect if image is transparent.
389
		 *
390
		 * That's very inaccurate since it doesn't include images which support transparency
391
		 *
392
		 * @param mimeType
393
		 * @returns {boolean}
394
		 * @private
395
		 */
396
		_isTransparent: function (mimeType) {
397
			return !(mimeType === 'image/jpeg'
398
				|| mimeType === 'image/x-dcraw'
399
				|| mimeType === 'application/font-sfnt'
400
				|| mimeType === 'application/x-font'
401
			);
402
		},
403
404
		/**
405
		 * Changes the browser Url, based on the current image
406
		 *
407
		 * @param {string} path
408
		 * @private
409
		 */
410
		_setUrl: function (path) {
411
			if (history && history.replaceState) {
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable history is declared in the current environment, consider using typeof history === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
412
				history.replaceState('', '', '#' + encodeURI(path));
413
			}
414
		},
415
416
		/**
417
		 * Hides the current image (before loading the next)
418
		 *
419
		 * @private
420
		 */
421
		_hideImage: function () {
422
			this.zoomablePreviewContainer.empty();
423
			this.controls.hideActionButtons();
424
		},
425
426
		/**
427
		 * Retrieves an SVG
428
		 *
429
		 * An SVG can't be simply attached to a src attribute like a bitmap image
430
		 *
431
		 * @param {string} source
432
		 *
433
		 * @returns {*}
434
		 * @private
435
		 */
436
		_getSVG: function (source) {
437
			var svgPreview = null;
438
			// DOMPurify only works with IE10+ and we load SVGs in the IMG tag
439
			if (window.btoa &&
440
				document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image",
441
					"1.1")) {
442
				var xmlHttp = new XMLHttpRequest();
0 ignored issues
show
Bug introduced by
The variable XMLHttpRequest seems to be never declared. If this is a global, consider adding a /** global: XMLHttpRequest */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
443
				xmlHttp.open("GET", source, false);
444
				xmlHttp.send(null);
445
				if (xmlHttp.status === 200) {
446
					var pureSvg = DOMPurify.sanitize(xmlHttp.responseText, {ADD_TAGS: ['filter']});
447
					// Remove XML comment garbage left in the purified data
448
					var badTag = pureSvg.indexOf(']&gt;');
449
					var fixedPureSvg = pureSvg.substring(badTag < 0 ? 0 : 5, pureSvg.length);
450
					svgPreview = "data:image/svg+xml;base64," + window.btoa(fixedPureSvg);
451
				}
452
			}
453
454
			return svgPreview;
455
		},
456
457
		/**
458
		 * Retrieves the slideshow's template
459
		 *
460
		 * @returns {*}
461
		 * @private
462
		 */
463
		_getSlideshowTemplate: function () {
464
			var defer = $.Deferred();
465
			if (!this.$slideshowTemplate) {
466
				var self = this;
467
				var url = OC.generateUrl('apps/gallery/slideshow', null);
468
				$.get(url, function (tmpl) {
469
						var template = $(tmpl);
470
						var tmplButton;
471
						var buttonsArray = [
472
							{
473
								el: '.next',
474
								trans: t('gallery', 'Next')
475
							},
476
							{
477
								el: '.play',
478
								trans: t('gallery', 'Play')
479
							},
480
							{
481
								el: '.pause',
482
								trans: t('gallery', 'Pause')
483
							},
484
							{
485
								el: '.previous',
486
								trans: t('gallery', 'Previous')
487
							},
488
							{
489
								el: '.exit',
490
								trans: t('gallery', 'Close')
491
							},
492
							{
493
								el: '.downloadImage',
494
								trans: t('gallery', 'Download'),
495
								toolTip: true
496
							},
497
							{
498
								el: '.changeBackground',
499
								trans: t('gallery', 'Toggle background'),
500
								toolTip: true
501
							},
502
							{
503
								el: '.deleteImage',
504
								trans: t('gallery', 'Delete'),
505
								toolTip: true
506
							}
507
						];
508
						for (var i = 0; i < buttonsArray.length; i++) {
509
							var button = buttonsArray[i];
510
511
							tmplButton = template.find(button.el);
512
							tmplButton.val(button.trans);
513
							if (button.toolTip) {
514
								tmplButton.attr("title", button.trans);
515
							}
516
						}
517
						self.$slideshowTemplate = template;
518
						defer.resolve(self.$slideshowTemplate);
519
					})
520
					.fail(function () {
521
						defer.reject();
522
					});
523
			} else {
524
				defer.resolve(this.$slideshowTemplate);
525
			}
526
			return defer.promise();
527
		}
528
	};
529
530
	window.SlideShow = SlideShow;
531
})(jQuery, OC, OCA, t);
532